package com.ajk.server.service.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.alibaba.fastjson.JSONObject;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.ajk.server.controller.events.CreditsEvent;
import com.ajk.server.dao.PatientEventDayMapper;
import com.ajk.server.exception.AjkException;
import com.ajk.server.exception.ErrorConstant;
import com.ajk.server.model.CreditsCategory;
import com.ajk.server.model.PatientEventDay;
import com.ajk.server.model.query.PatientEventDayQuery;
import com.ajk.server.model.request.PatientEventCategory;
import com.ajk.server.service.PatientEventDayService;
import com.ajk.server.utils.JodaTimeUtil;
import com.ajk.server.utils.RedisKeyUtil;
import com.ajk.server.utils.SpringContextUtil;

/**
 * 用户健康数据-每种类型，每天只记录一条  <br>
 * t_patient_event_day <br>
 * v1.0 2016-05-22
 */
@Service
public class PatientEventDayServiceImpl implements PatientEventDayService {

    private final static int LIMIT = 22;

    private static final Logger logger = LoggerFactory.getLogger(PatientEventDayServiceImpl.class);

    @Autowired
    private PatientEventDayMapper patientEventDayMapper;

    @Autowired
    RedisTemplate<String, Object> template;

    /**
     * 根据ID删除 用户健康数据-每种类型，每天只记录一条
     */
    @Override
    public Integer deletePatientEventDayById(Integer id) {
        return patientEventDayMapper.deleteById(id);
    }

    /**
     * 新增 用户健康数据-每种类型，每天只记录一条
     */
    @Override
    @Transactional
    public Integer batchInsertPatientEventDay(List<? extends PatientEventDay> patientEventDays) {
        Integer count = patientEventDayMapper.batchInsert(patientEventDays);
        return count;
    }

    private int getTimeBlockI(String content) {
        int result = -1;
        if (StringUtils.isNotEmpty(content)) {
            JSONObject contentJson = com.alibaba.fastjson.JSON.parseObject(content);
            Integer timeBlockId = contentJson.getInteger("timeBlockId");
            if (timeBlockId != null)
                result = timeBlockId;
        }
        return result;
    }

    /**
     * 新增 用户健康数据-每种类型，每天只记录一条
     */
    @Override
    @Transactional
    public Integer insertPatientEventDay(PatientEventDay domain) {

        Integer userId = domain.getUserId();
        Integer categoryId = domain.getCategoryId();
        Date actionTime = domain.getActionTime();
        PatientEventDay dayData = null;
        Integer id = null;
        int dominBsTime = getTimeBlockI(domain.getContent());
        if (categoryId == 3) {
            dayData = patientEventDayMapper.findSugarBytimeBlockIdAndActionTime(userId,dominBsTime,actionTime);
        }else{
            dayData = patientEventDayMapper.findByCategoryIdAndActionTime(userId, categoryId, actionTime);
        }
        if (dayData != null) {
            domain.setId(dayData.getId());
            updatePatientEventDay(domain);
            id = domain.getId();
        } else {
            patientEventDayMapper.insert(domain);
            id = domain.getId();
            //缓存
            HashOperations<String, String, PatientEventDay> hashOperations = template.opsForHash();
            String key = RedisKeyUtil.userLastPatientEventMapKey(userId);

            String categoryKey = PatientEventCategory.getPatientEventCategoryByValue(categoryId).name();
            if(dominBsTime==2){
                categoryKey = categoryKey+"_2";
            }

            PatientEventDay pEvent = hashOperations.get(key, categoryKey);
            if (pEvent != null) {
                Date actionTimeRedis = pEvent.getActionTime();
                DateTime rDTime = new DateTime(actionTimeRedis.getTime());
                DateTime pDTime = new DateTime(actionTime.getTime());
                if (!rDTime.isAfter(pDTime)) {
                    template.opsForHash().put(key, categoryKey, domain);
                    //					template.expire(key, 7, TimeUnit.DAYS);
                }
            } else {
                PatientEventDay dayDatad = patientEventDayMapper.findLastByCategoryId(userId, categoryId);
                template.opsForHash().put(key, categoryKey, dayDatad);
                //				template.expire(key, 7, TimeUnit.DAYS);
            }
            //添加积分
            CreditsEvent event = new CreditsEvent(id, userId, CreditsCategory.PATIENT_EVENT_ADD);
            SpringContextUtil.getApplicationContext().publishEvent(event);

            syncSingletonBloodPressureInRedis(id);
            String userBloodPressureZSetKey = RedisKeyUtil.userBloodPressureZSetKey(userId);
            if (!template.hasKey(userBloodPressureZSetKey)) {
                template.opsForZSet().add(userBloodPressureZSetKey, Integer.MAX_VALUE, -Long.MAX_VALUE * 1.0D);
            }
            template.opsForZSet().add(userBloodPressureZSetKey, id, -actionTime.getTime() * 1.0D);
            template.opsForZSet().removeRange(userBloodPressureZSetKey, (long) LIMIT, -1L);
        }
        return id;
    }

    /**
     * 更新 用户健康数据-每种类型，每天只记录一条
     */
    @Override
    @Transactional
    public Integer updatePatientEventDay(PatientEventDay domain) {
        Integer id = domain.getId();
        Date actionTime = domain.getActionTime();

        PatientEventDay db = patientEventDayMapper.findById(id);
        if (db == null) {
            AjkException exception = new AjkException(ErrorConstant.OPERATION_ERROR.getErrorCode(), "修改的记录不存在");
            logger.info("修改健康数据失败", exception);
            throw exception;
        }
        Date dbActionTime = db.getActionTime();
        Integer userId = domain.getUserId();
        Integer categoryId = domain.getCategoryId();

        if (!JodaTimeUtil.isSameDay(actionTime, dbActionTime)) {//那个时间不等
            PatientEventDay dayData = patientEventDayMapper.findByCategoryIdAndActionTime(userId, categoryId, actionTime);
            if (dayData != null) { //需要删除此条记录
                Integer deleteId = dayData.getId();
                patientEventDayMapper.deleteById(deleteId);
                syncSingletonBloodPressureInRedis(deleteId);
            }
        }

        patientEventDayMapper.update(domain);
        //修改
        HashOperations<String, String, PatientEventDay> hashOperations = template.opsForHash();
        String key = RedisKeyUtil.userLastPatientEventMapKey(userId);
        PatientEventDay pEvent = hashOperations.get(key, PatientEventCategory.getPatientEventCategoryByValue(categoryId).name());
        if (pEvent != null) {
            Date actionTimeRedis = pEvent.getActionTime();
            DateTime rDTime = new DateTime(actionTimeRedis.getTime());
            DateTime pDTime = new DateTime(actionTime.getTime());
            if (!rDTime.isAfter(pDTime)) {
                template.opsForHash().put(key, PatientEventCategory.getPatientEventCategoryByValue(categoryId).name(), domain);
            }
        } else {
            //取最近的一条
            PatientEventDay dayDatad = patientEventDayMapper.findLastByCategoryId(userId, categoryId);
            template.opsForHash().put(key, PatientEventCategory.getPatientEventCategoryByValue(categoryId).name(), dayDatad);
            //			template.expire(key, 7, TimeUnit.DAYS);
        }

        if (categoryId.intValue() == PatientEventCategory.BLOOD_PRESSURE.getValue()) {
            syncSingletonBloodPressureInRedis(id);
            syncBloodPressureByUserId(userId);
        }

        return 1;
    }

    /**
     * 根据ID查询 用户健康数据-每种类型，每天只记录一条
     */
    public PatientEventDay findPatientEventDayById(Integer id) {
        PatientEventDay result = patientEventDayMapper.findById(id);
        if (result != null) {
            result.dealWith();
        }
        return result;
    }

    /**
     * 通过query 查询 用户健康数据-每种类型，每天只记录一条
     */
    public List<PatientEventDay> findPatientEventDayByQuery(PatientEventDayQuery query) {
        query.dealWith();
        List<PatientEventDay> result = patientEventDayMapper.findByQuery(query);
        if (CollectionUtils.isNotEmpty(result)) {
            for (PatientEventDay patientEventDay : result) {
                patientEventDay.dealWith();
            }
        }
        return result;
    }

    /**
     * 通过query count总条数
     */
    public Integer countPatientEventDayByQuery(PatientEventDayQuery query) {
        query.dealWith();
        Integer result = patientEventDayMapper.countByQuery(query);
        return result;
    }

    /**
     * 通过ids查询
     */
    public List<PatientEventDay> findPatientEventDayByIds(List<Integer> ids) {
        List<PatientEventDay> result = patientEventDayMapper.findByIds(ids);
        if (CollectionUtils.isNotEmpty(result)) {
            for (PatientEventDay patientEventDay : result) {
                patientEventDay.dealWith();
            }
        }
        return result;
    }

    public void syncBloodPressureByUserId(Integer userId) {
        List<PatientEventDay> result = findLastNByUserId(userId, LIMIT - 1, PatientEventCategory.BLOOD_PRESSURE.getValue());
        String userBloodPressureZSetKey = RedisKeyUtil.userBloodPressureZSetKey(userId);
        Set<TypedTuple<Object>> set = new HashSet<TypedTuple<Object>>();
        DefaultTypedTuple<Object> tuple = new DefaultTypedTuple<Object>(Integer.MAX_VALUE, -Long.MAX_VALUE * 1.0D);
        set.add(tuple);
        if (CollectionUtils.isNotEmpty(result)) {
            for (PatientEventDay patientEventDay : result) {
                tuple = new DefaultTypedTuple<Object>(patientEventDay.getId(), -patientEventDay.getActionTime().getTime() * 1.0D);
                set.add(tuple);
            }
            if (CollectionUtils.isNotEmpty(set)) {
                template.opsForZSet().add(userBloodPressureZSetKey, set);
            }
        }
        template.delete(userBloodPressureZSetKey);
        template.opsForZSet().add(userBloodPressureZSetKey, set);
    }

    public void syncBloodPressureInRedis(boolean force) {
        String key = RedisKeyUtil.bloodPressureZSetForHashKey();
        if (force || (!template.hasKey(key))) {
            template.delete(key);
            List<PatientEventDay> result = findByCategoryId(PatientEventCategory.BLOOD_PRESSURE.getValue());
            Map<String, PatientEventDay> map = new HashMap<String, PatientEventDay>();
            if (CollectionUtils.isNotEmpty(result)) {
                for (PatientEventDay patientEventDay : result) {
                    String hashKey = RedisKeyUtil.domainIdKey(patientEventDay.getId());
                    map.put(hashKey, patientEventDay);
                }
                if (!map.isEmpty()) {
                    template.opsForHash().putAll(key, map);
                }
            }
        }
    }

    public void syncSingletonBloodPressureInRedis(Integer bloodPressureId) {
        String key = RedisKeyUtil.bloodPressureZSetForHashKey();
        if (!template.hasKey(key)) {
            syncBloodPressureInRedis(true);
        } else {
            PatientEventDay patientEventDay = patientEventDayMapper.findById(bloodPressureId);
            String hashKey = RedisKeyUtil.domainIdKey(bloodPressureId);
            if (patientEventDay != null && patientEventDay.getCategoryId().intValue() == PatientEventCategory.BLOOD_PRESSURE.getValue()) {
                template.opsForHash().put(key, hashKey, patientEventDay);
            } else {
                template.opsForHash().delete(key, hashKey);
            }
        }
    }

    public List<PatientEventDay> findLastNByUserId(Integer userId, Integer limit, Integer categoryId) {
        return patientEventDayMapper.findLastNByUserId(userId, limit, categoryId);
    }

    public List<PatientEventDay> findByCategoryId(Integer categoryId) {
        return patientEventDayMapper.findAllPatientEventDayByCategoryId(categoryId);
    }


    @Override
    public List<PatientEventDay> findWeekBloodPress(Integer userId) {
        List<PatientEventDay> result = null;
        String userBloodPressureZSetKey = RedisKeyUtil.userBloodPressureZSetKey(userId);
        if (!template.hasKey(userBloodPressureZSetKey)) {
            syncBloodPressureByUserId(userId);
        }
        syncBloodPressureInRedis(false);
        Set<Object> idSet = template.opsForZSet().range(userBloodPressureZSetKey, 1L, -1L);
        if (CollectionUtils.isNotEmpty(idSet)) {
            List<String> list = new ArrayList<String>();
            for (Object object : idSet) {
                String hashKey = RedisKeyUtil.domainIdKey(object);
                list.add(hashKey);
            }
            if (CollectionUtils.isNotEmpty(list)) {
                String key = RedisKeyUtil.bloodPressureZSetForHashKey();
                HashOperations<String, String, PatientEventDay> hOperations = template.opsForHash();
                result = hOperations.multiGet(key, list);
                if (CollectionUtils.isNotEmpty(result)) {
                    for (int i = result.size() - 1; i >= 0; i--) { //排除空
                        if (!(result.get(i) instanceof PatientEventDay)) {
                            result.remove(i);
                        }
                    }

                    Collections.sort(result, new Comparator<PatientEventDay>() {

                        @Override
                        public int compare(PatientEventDay o1, PatientEventDay o2) {
                            if (o1 != null && o2 != null && o1.getActionTime() != null && o2.getActionTime() != null) {
                                return o1.getActionTime().compareTo(o2.getActionTime());
                            }
                            return 0;
                        }
                    });
                }
            }
        }
        return result;
    }


    @Override
    public List<PatientEventDay> findWeekBloodSugar(Integer userId) {
       return patientEventDayMapper.findLastSugarByWeekCategoryId(userId);
    }

}	